# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##--------------------------------------------------------------------------##
__docformat__ = 'restructuredtext en'


def __checkAdjacent(cards, condition):
    """
    __checkAdjacent(cards:list, condition:str) -> bool

    Checks neighbor cards meet passed condition.

    .. note : conditions are False statements about cards for speed.
    """
    for c1, c2 in zip(cards[:-1], cards[1:]):
        if eval(condition):
            return False
    return True


def cardsFaceUp(cards):
    """
    Checks cards are face-up.

    :param sequence cards: card sequence
    :return: face-up?
    :rtype: boolean
    """
    return all([c.face_up for c in cards]) if cards else False


def cardsFaceDown(cards):
    """
    cardsFaceDown(cards:seq) -> bool

    Checks cards are face-down.

    :param sequence cards: card sequence
    :return: face-down?
    :rtype: boolean
    """
    return all([not c.face_up for c in cards]) if cards else False


def isRankSequence(cards, mod=8192, dir=-1, **cap):
    """
    isRankSequence(cards:seq[, mod:int=8192, dir:int=-1]) -> bool

    Checks cards are face-up and built in rank order.
    """
    return (__checkAdjacent(cards,
            "(c1['rank'] + {0}) % {1} != c2['rank']".format(dir, mod))
            if cardsFaceUp(cards) else False)


def isEitherRankSequence(cards, mod=8192, dir=-1, **cap):
    """
    isRankSequence(cards:seq[, mod:int=8192, dir:int=-1]) -> bool

    Checks cards are face-up and built in either rank order.
    """
    either = "(c1['rank'] + {0}) % {1}, (c1['rank'] - {0}) % {1}".format(dir, mod)
    return (__checkAdjacent(cards,
            "c2['rank'] not in ({0}, )".format(either))
            if cardsFaceUp(cards) else False)


def isSuitSequence(cards, mod=8192, dir=-1, **cap):
    """
    isSuitSequence(cards:seq[, mod:int=8192, dir:int=-1]) -> bool

    Checks cards are face-up and built in suit order.
    """
    return (__checkAdjacent(cards,
            "(c1['suit'] + {0}) % {1} != c2['suit']".format(dir, mod))
            if cardsFaceUp(cards) else False)


def isRankSuitSequence(cards, mod=8192, dir=-1, **cap):
    """
    isRankSuitSequence(cards:seq[, mod:int=8192, dir:int=-1]) -> bool

    Checks cards are face-up and built in suit order by same rank.
    """
    return (__checkAdjacent(cards, "c1['rank'] == c2['rank']")
            if isSuitSequence(cards, mod, dir) else False)


def isSameRank(cards, rank=None):
    """
    isSameRank(cards:seq[, rank:int=None]) -> bool

    Checks cards are face-up and are same rank. If rank used, it
    specifies a rank to match.
    """
    if not cardsFaceUp(cards):
        return False
    return (__checkAdjacent(cards,
            "c1['rank'] != {0} != c2['rank']".format(rank)
            if rank is not None else "c1['rank'] != c2['rank']"))


def isSameSuit(cards, suit=None):
    """
    isSameSuit(cards:seq[, suit:int=None]) -> bool

    Checks cards are face-up and are same suit. If suit used, it
    specifies a suit to match.
    """
    if not cardsFaceUp(cards):
        return False
    return (__checkAdjacent(cards,
            "c1['suit'] != {0} != c2['suit']".format(suit)
            if suit is not None else "c1['suit'] != c2['suit']"))


def isSameColor(cards, color=None):
    """
    isSameColor(cards:seq[, color:int=None]) -> bool

    Checks cards are face-up and are same color. If color used, it
    specifies a color to match.
    """
    if not cardsFaceUp(cards):
        return False
    return (__checkAdjacent(cards,
            "c1['color'] != {0} != c2['color']".format(color))
            if color is not None else "c1['suit'] != c2['suit']")


def isAlternateColorSequence(cards, mod=8192, dir=-1, **cap):
    """
    isAlternateColorSequence(cards:seq[, mod:int=8192, dir:int=-1]) -> bool

    Checks cards are face-up and built in rank order by alt color.
    """
    return (__checkAdjacent(cards, "c1['color'] == c2['color']")
            if isRankSequence(cards, mod, dir) else False)


def isSameColorSequence(cards, mod=8192, dir=-1, **cap):
    """
    isSameColorSequence(cards:seq[, mod:int=8192, dir:int=-1]) -> bool

    Checks cards are face-up and built in rank order by same color.
    """
    return (__checkAdjacent(cards, "c1['color'] != c2['color']")
            if isRankSequence(cards, mod, dir) else False)


def isSameSuitSequence(cards, mod=8192, dir=-1, **cap):
    """
    isSameSuitSequence(cards:seq[, mod:int=8192, dir:int=-1]) -> bool

    Checks cards are face-up and built in rank order by same suit.
    """
    return (__checkAdjacent(cards, "c1['suit'] != c2['suit']")
            if isRankSequence(cards, mod, dir) else False)


def isAnySuitButOwnSequence(cards, mod=8192, dir=-1, **cap):
    """
    isAnySuitButOwnSequence(cards:seq[, mod:int=8192, dir:int=-1]) -> bool

    Checks cards are face-up and built in rank order by alt suit.
    """
    return (__checkAdjacent(cards, "c1['suit'] == c2['suit']")
            if isRankSequence(cards, mod, dir) else False)


def isSameForceSequence(cards, mod=8192, dir=-1, **cap):
    """
    isSameForceSequence(cards:seq[, mod:int=8192, dir:int=-1])
    -> bool

    Checks cards are face-up and built in rank order by same force.
    """
    return (__checkAdjacent(cards, "c1['force'] == c2['force']")
            if isRankSequence(cards, mod, dir) else False)


def isAlternateForceSequence(cards, mod=8192, dir=-1, **cap):
    """
    isAlternateForceSequence(cards:seq[, mod:int=8192, dir:int=-1])
    -> bool

    Checks cards are face-up and built in rank order by alt force.
    """
    return (__checkAdjacent(cards, "c1['force'] != c2['force']")
            if isRankSequence(cards, mod, dir) else False)


def isSequenceWithRANKS(cards, ranks, mod=8192, dir=-1, **cap):
    """
    """
    if not cardsFaceUp(cards):
        return False
    for c1, c2 in zip(cards[:-1], cards[1:]):
        if (ranks.index(c1[0]['rank']) + dir) % mod != ranks.index(c2[1]['rank']):
            return False
    return True


def isRankMultipleSequence(cards, multi=2, mod=8192, dir=-1, **cap):
    """
    isRankSequence(cards:seq[, mod:int=8192, dir:int=-1]) -> bool

    Checks cards are face-up and built in rank order.
    """
    return (__checkAdjacent(cards,
            "({0} * c1['rank'] + {1}) % {2} != c2['rank']".format(multi, dir, mod))
            if cardsFaceUp(cards) else False)


def getDirection(cards, attr='rank', **cap):
    """
    getDirection(cards:seq[, attr:str='rank', *cap*]) -> int

    Checks cards are face-up and then calculates the cards sequence direction.
    """
    if not cardsFaceUp(cards):
        return False
    dir_ = 0
    mod = cap['mod']
    if len(cards) > 1:
        dir_ = (cards[-1][attr] - cards[-2][attr]) % mod
        if dir_ > mod / 2:
            return dir_ - mod
    return dir_


def sumOf(cards, value, attr='rank'):
    """
    sumOf(cards:seq, value:int[, attr:str='rank']) -> bool

    Checks cards are face-up and sum of cards attr is the value.
    """
    if not cardsFaceUp(cards):
        return False
    return (not cards or
            sum([c['attr'] + 1 for c in cards]) - 1 == value)


isGaji = lambda c: (c['suit'], c['rank']) == (10, 3)

isTrump = lambda c: c['suit'] == 4


def isFlowerSequence(cards, strict=True, **cap):
    """
    isFlowerSequence(cards:seq[. strict:bool=True, mod:int=8192, dir:int=-1])
      -> bool

    Check cards are face-up and built in rank order with trash cards swapable.
    """
    for c1, c2 in zip(cards[:-1], cards[1:]):
        if not isRankSequence(swapTrashCards(c1, c2, strict, **cap), **cap):
            return False
    return True


def swapTrashCards(card1, card2, strict=True, mod=8192, dir=-1, **cap):
    """
    swapTrashCards(cards1:obj, card2:obj[strict=True, mod:int=8192,
      dir:int=-1]) -> bool

    Check card1 is not WILLOW or strict and that card1 and card2 has either
    THIRD or FOURTH rank card allowing either to build as the other.

    .. note : method supports wrapping and either direction.
    """
    if (card1['suit'] == 10 or strict):
        return card1, card2
    for pairs in ([[3, 2], [1, 3]] + [3, 0] if mod == 4 else []):
        if dir < 0:
            pairs.reverse()
        if (card1['rank'], card2['rank']) in pairs:
            return card2, card1
    return card1, card2


def isSameMahjonggRank(cards):
    assert len(cards) == 2
    card1, card2 = cards
    if card1['suit'] != card2['suit']:
        return False
    if card1['suit'] == 3:
        if card1['rank'] >= 8:  # Seasons
            return card2['rank'] >= 8
        if card1['rank'] >= 4:  # Flowers
            return 7 >= card2['rank'] >= 4
    return card1['rank'] == card2['rank']


def highestRank(gi, suit):
    return (len(gi['ranks']) if suit < len(gi['suits'])
            else len(gi['trumps'])) - 1


def groupTakes(stacks, from_stack):
    if not from_stack.cards:
        return
    for pile in stacks:
        if (from_stack is not pile and
            pile.cardsBuild(from_stack, from_stack.cards[-1:])):
            return pile